/******************************************************************************************
 * Import
 ******************************************************************************************/

// module
import _ from 'lodash';

/******************************************************************************************
 * Export Class
 ******************************************************************************************/

/**
 * 请求设置对象结构：
 * {
 *      type: string,           // 请求的类型，默认为json，也可以定义为text（默认为json）
 *      parse: string,          // 解析类型，默认为heshequ（和社区restful格式），第三方则是别的名称
 *      trace: boolean,         // 是否在console中追踪response，（默认为false）
 *      url: string,            // url地址,
 *      header: object,         // 需要携带的header对象
 *      data: object,           // 参数键值对,
 *      indicator: {            // 指示器（吐司菜单）对象（发送http请求时显示，完成http请求时隐藏）
 *          show(),
 *          hide()
 *      },
 *      error: {                // 错误对象，对逻辑错误、服务端错误、网络错误的统一处理
 *          code(err) {},
 *          server(err) {},
 *          net(err) {}
 *      }
 *      success(data)=> {},     // 成功回调函数
 * }
 */
export default class Http {

    /*------------------------------------------------------------------------------------
	 * 外部调用接口
	 *-----------------------------------------------------------------------------------*/
    /**
     * get请求，参数请看_request方法
     * 
     * @param {*} obj 访问请求设置对象 
     */
    static get(obj) {
        obj['method'] = 'GET';
        this.request(obj);
    }

    /**
     * post请求，参数请看_request方法
     * 
     * @param {*} obj 访问请求设置对象 
     */
    static post(obj) {
        obj['method'] = 'POST';
        this.request(obj);
    }

    /*------------------------------------------------------------------------------------
	 * 真实执行http
	 *-----------------------------------------------------------------------------------*/
    /**
     * 真实访问方法
     * 
     * @param {*} method 访问方法
     * @param {*} obj 访问请求设置对象 
     */
    static request(obj) {

        // 显示指示器
        this._indicatorShow(obj.indicator);

        /**
         * 第1步：设置url（主要用于get）
         */
        if (obj.method == 'GET') {
            obj.url = obj.url + this._getParamGet(obj);
        }
        
        /**
         * 第2步：设置body（主要用于post）
         */
        if (obj.method == 'POST') {
            result = this._getParamPost(obj);

            // 如果不需要添加body字段，则this._getParamPost函数返回的是false
            if (result != false) {
                obj['body'] = result;
            }
        }

        /**
         * 第3步：根据obj，设置config
         */
        let config = {
            method: obj.method
        };
        if (obj.header) config['headers'] = obj.header;
        if (obj.body) config['body'] = obj.body;

        /**
         * 第4步：请求
         */
        fetch(obj.url, config)

        /**
         * 第5步：请求返回
         */
        .then((res)=> {

            // 如果打开了trace，则输出response信息
            if (_.isBoolean(obj.trace) && obj.trace) {
                console.log('--------- ' + obj.method + ' : ' + obj.url);
                console.log(res);
                console.log('--------- END');
            }

            // 返回码判断，如果不等于200，则：收集错误，调用错误回调，并直接返回
            if (parseInt(res.status) != 200) {
                this._collectServerError(res, obj);
                if (_.isObject(obj.error) && _.isFunction(obj.error.server)) {
                    obj.error.server(res, obj);
                }
                return null;
            }

            // 将数据返回给第6步（注意，这里全部返回为text类型）
            return res.text();   
        })

        /**
         * 第6步：数据处理
         */
        .then((res)=> {

            // 隐藏指示器
            this._indicatorHide(obj.indicator);

            // 空值判断，返回码不为200时，res为null，则直接返回
            if (_.isNull(res)) {
                return;
            }

            // 请求类型判断，如果是text，则调用success，并直接返回
            if (_.isString(obj.type) && obj.type == 'text') {
                if (_.isFunction(obj.success)) {
                    obj.success(res, obj);
                }
                return;
            }

            // 字符串格式判断，如果首字母和尾字母并不是"{"和"}"，则说明服务器已经出错，收集错误，调用错误回调，并直接返回
            // 否则转换：res = JSON.parse(res)
            if (res[0] != '{' || res[res.length - 1] != '}') {
                this._collectServerError(res, obj);
                if (_.isObject(obj.error) && _.isFunction(obj.error.server)) {
                    obj.error.server(res, obj);
                }
                return;
            }
            res = JSON.parse(res);

            // json格式判断，如果返回没有带code字段，则说明服务器出错，惧集错误，调用错误回调，并直接返回
            if (_.isUndefined(res.code)) {
                this._collectServerError(res, obj);
                if (_.isObject(obj.error) && _.isFunction(obj.error.server)) {
                    obj.error.server(res, obj);
                }
                return;
            }

            // 返回值带有code，则服务器返回一定是正常的了
            
            // 逻辑错误判断，如果code不等于0，则说明存在逻辑错误，收集错误，调用错误回调，并直接返回
            if (parseInt(res.code) != 0) {
                this._collectCodeError(res, obj);
                if (_.isObject(obj.error) && _.isFunction(obj.error.code)) {
                    obj.error.code(res, obj);
                }
                return;
            }

            // 已经全部正确正确，调用success回调
            if (_.isFunction(obj.success)) {
                obj.success(res, obj);
            }
        })

        /**
         * 第7步：错误抓取
         */
        .catch((err) => {

            // 隐藏指示器
            this._indicatorHide(obj.indicator);

            // 收集错误，并调用错误回调
            this._collectNetError(err, obj);
            if (_.isObject(obj.error) && _.isFunction(obj.error.net)) {
                obj.error.net(err, obj);
            } 
        });
    }

    /*------------------------------------------------------------------------------------
	 * 参数收集方法
	 *-----------------------------------------------------------------------------------*/
    /**
     * 设置Get方法的参数
     * 
     * @param {Object} obj 设置对象
     */
    static _getParamGet(obj) {

        // 如果不是平面对象，则直接返回空串
        if (!_.isPlainObject(obj.data)) return '';

        // 计算params数组
        let params = [];
        for (let key in obj.data) {
            params.push(key + '=' + obj.data[key]);
        }

        // 计算得到连接符号和查询字符串
        const joinSymbol = obj.url.indexOf('?') === -1 ? '?' : '&';
        const queryString = params.join('&');

        // 返回连接在url之后需要连接的参数字符串
        return joinSymbol + queryString;
    }

    /**
     * 设置Post方法的参数
     * 
     * @param {Object} obj 设置对象
     */
    static _getParamPost(obj) {

        // 如果不是平面对象，则直接返回false（代表不需要在obj添加body）
        if (!_.isPlainObject(obj.data)) return false;

        // 制作formData对象，并返回
        let formData = new FormData();
        for (let key in obj.data) {
            formData.append(key, obj.data[key])
        }

        // 返回formData数据（代表需要在obj添加body）
        return formData;
    }

    /*------------------------------------------------------------------------------------
	 * 访问指示器显示与隐藏方法
	 *-----------------------------------------------------------------------------------*/
    /**
     * 如果带有指示器，则显示指示器
     * 注意show和hide都必须是方法
     * 
     * @param {Object} indicator indicator对象
     */
    static _indicatorShow(indicator) {
        if (!_.isObject(indicator)) return;
        if (_.isFunction(indicator.show) && _.isFunction(indicator.hide)) {
            indicator.show();
        }
    }

    /**
     * 如果带有指示器，则隐藏指示器
     * 注意show和hide都必须是方法
     * 
     * @param {Object} indicator indicator对象
     */
    static _indicatorHide(indicator) {
        if (!_.isObject(indicator)) return;
        if (_.isFunction(indicator.show) && _.isFunction(indicator.hide)) {
            indicator.hide();
        }
    }

    /*------------------------------------------------------------------------------------
	 * 错误收集方法
	 *-----------------------------------------------------------------------------------*/
    /**
     * 收集逻辑错误
     * 
     * @param {Object} res 从服务器返回的result数据对象，主要取code和msg
     */
    static _collectCodeError(res, obj) {
        // console.log(res);
    }

    /**
     * 收集服务器错误
     * 
     * @param {Object} res 从服务器返回的对象，主要取状态码和错误消息
     */
    static _collectServerError(res, obj) {
        // console.log(res);
    }

    /**
     * 收集网络错误
     * 
     * @param {String} err 网络错误消息
     */
    static _collectNetError(err, obj) {
        // console.log(err);
    }
}
